#include "file.h"
#include "fs.h"
#include "super_block.h"
#include "inode.h"
#include "stdio-kernel.h"
#include "memory.h"
#include "debug.h"
#include "interrupt.h"
#include "string.h"
#include "thread.h"
#include "global.h"

#define DEFAULT_SECS 1

// 文件表
struct file file_table[MAX_FILE_OPEN];

// 从文件表 file_table 中获取一个空闲位，返回下标
int32_t get_free_slot_in_global(void) {
    uint32_t fd_idx = 3; // 前面 0 1 2 是标准输入输出
    while(fd_idx < MAX_FILE_OPEN) {
        if(file_table[fd_idx].fd_inode == NULL) break;
        fd_idx++;
    }
    if(fd_idx == MAX_FILE_OPEN) {
        printk("exceed max open files.\n");
        return -1;
    }
    return fd_idx;
}

// 将全局描述符下标安装到进程或线程自己的文件描述符数组 fd_table 中，返回其下标
int32_t pcb_fd_install(int32_t global_fd_idx) {
    struct task_struct* cur = running_thread();
    uint8_t local_fd_idx = 3; // 跨过标准输入输出
    while(local_fd_idx < MAX_FILES_OPEN_PER_PROC) {
        if(cur -> fd_table[local_fd_idx] == -1) { // 查找空闲位
            cur -> fd_table[local_fd_idx] = global_fd_idx;
            break;
        }
        local_fd_idx++;
    }
    if(local_fd_idx == MAX_FILES_OPEN_PER_PROC) {
        printk("exceed max open files_per_proc\n");
        return -1;
    }
    return local_fd_idx;
}

// 分配一个 inode，返回 inode 编号（位图索引）
int32_t inode_bitmap_alloc(struct partition* part) {
    int32_t bit_idx = bitmap_scan(&part -> inode_bitmap, 1);
    if(bit_idx == -1) return -1;
    bitmap_set(&part -> inode_bitmap, bit_idx, 1);
    return bit_idx;
}

// 分配一个扇区，返回其扇区地址
int32_t block_bitmap_alloc(struct partition* part) {
    int32_t bit_idx = bitmap_scan(&part -> block_bitmap, 1);
    if(bit_idx == -1) return -1;
    bitmap_set(&part -> block_bitmap, bit_idx, 1);
    return (part -> sb -> data_start_lba + bit_idx);
}

// 将内存中 bitmap 第 bit_idx 位所在的扇区同步到硬盘
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type) {
    /**
     * Tips
     * 4096 = 512字节 = 1扇区
     * 但在位图(数组)中是以 0 为起始
     * 因此第 4096 位占用两个扇区
     * 0~4095 为一个扇区
    */
    uint32_t off_sec = bit_idx / 4096; // 求得 bit_idx 所在扇区
    uint32_t off_size = off_sec * BLOCK_SIZE; // 字节偏移量
    uint32_t sec_lba;     // 起始地址
    uint8_t* bitmap_off;  // 内容的起始地址

    // 需要被同步到硬盘的位图只有 inode_bitmap 和 block_bitmap
    switch(btmp_type) {
        case INODE_BITMAP:
            sec_lba = part -> sb -> inode_bitmap_lba + off_sec;
            bitmap_off = part -> inode_bitmap.bits + off_size;
            break;
        case BLOCK_BITMAP:
            sec_lba = part -> sb -> block_bitmap_lba + off_sec;
            bitmap_off = part -> block_bitmap.bits + off_size;
            break;
    }

    ide_write(part -> my_disk, sec_lba, bitmap_off, 1);
}

// 创建文件，成功则返回文件描述符
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag) {
    void* io_buf = sys_malloc(1024);
    if(io_buf == NULL) {
        printk("in file_creat: sys_malloc for io_buf failed\n");
        return -1;
    }

    uint8_t rollback_step = 0; // 回滚点

    // 为新文件分配 inode
    int32_t inode_no = inode_bitmap_alloc(cur_part);
    if(inode_no == -1) {
        printk("in file_creat: allocate inode failed\n");
        return -1;
    }

    /**
     * 此 inode 要从堆中申请内存，不可申请局部变量，因为函数生命周期结束后会释放
     * 因为 file_table 数组中的文件描述符的 inode 指针要指向它
     */
    struct inode* new_file_inode = (struct inode*) sys_malloc(sizeof(struct inode));
    if(new_file_inode == NULL) {
        printk("file_create: sys_malloc for inode failded\n");
        rollback_step = 1;
        goto rollback;
    }
    inode_init(inode_no, new_file_inode); // 初始化 inode

    // 得到全局的文件表空闲位
    int fd_idx = get_free_slot_in_global();
    if(fd_idx == -1) {
        printk("exceed max open files\n");
        rollback_step = 2;
        goto rollback;
    }

    // 初始化文件表信息
    file_table[fd_idx].fd_inode = new_file_inode;
    file_table[fd_idx].fd_pos = 0;
    file_table[fd_idx].fd_flag = flag;
    file_table[fd_idx].fd_inode->write_deny = false;

    struct dir_entry new_dir_entry;
    memset(&new_dir_entry, 0, sizeof(struct dir_entry));

    create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry); // create_dir_entry只是内存操作不出意外,不会返回失败，因此不需要回滚（虽然我感觉这样不妥当？）

    // 同步目录项
    if(!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
        printk("sync dir_entry to disk failed\n");
        rollback_step = 3;
        goto rollback;
    }

    memset(io_buf, 0, 1024);
    inode_sync(cur_part, parent_dir -> inode, io_buf); // 同步父目录的 inode
    memset(io_buf, 0, 1024);
    inode_sync(cur_part, new_file_inode, io_buf); // 同步新文件的 inode
    bitmap_sync(cur_part, inode_no, INODE_BITMAP); // 同步 inode_bitmap 位图

    // 将新 inode 添加入已打开的 inode 链表中
    list_push(&cur_part -> open_inodes, &new_file_inode -> inode_tag);
    new_file_inode -> i_open_cnt = 1; // 打开次数设置为 1

    sys_free(io_buf);
    return pcb_fd_install(fd_idx); // 安装到当前进程的文件描述符数组中，并且返回下标

    rollback:
        switch(rollback_step) {
            case 3:
                memset(&file_table[fd_idx], 0, sizeof(struct file));
            case 2:
                sys_free(new_file_inode);
            case 1:
                bitmap_set(&cur_part -> inode_bitmap, inode_no, 0);
                break;
        }
        sys_free(io_buf);
        return -1;
}
